Распределённые транзакции

В распределенной системе, бизнес логика будет охватывать несколько микросервисов со своими БД. В данной ситуации возникает потребность в возможности отката изменений сразу в нескольких БД с сохранением атомарности операций для согласованности БД.

Существует ряд решений этой задачи отличающиеся по уровню сложности и масштабируемости

Модульный монолит

В данном подходе каждый из микросервисов А и Б является отдельной библиотекой, которые устанавливаются в общее пространство и имеют доступ к одной и той же бд.

Поскольку они находятся в одном пространстве и используют одну бд, они могут выполнять обработку в рамках одной и той же транзакции.

Для обеспечения одной транзакции можно сделать для них один общий класс дающий возможность работать с БД.

Более того существует возможность полностью разделить два этих сервиса, даже в рамках одного монолита. Они могут смотреть на разные схемы, быть в разных пакетах, поддерживаться разными командами.

Pasted image 20250530014725.png

Плюсы Модульного монолита:

  • простая семантика транзакций, позволяющая легко обеспечивать консистентность данных

Минусы Модульного монолита:

  • единый рантайм сервисов А и Б не позволяет нам независимо деплоить и масштабировать модули, а также обеспечивать отказоустойчивость
  • логическое разделение таблиц в рамках одной бд не является надежным
  • транзакция увеличивает связность между сервисами

Двухфазный коммит

Технические требования для имплементации 2PC:

  • наличие менеджера транзакций, например Narayana
  • надежное хранилище транзакционных логов
  • все бд, к которым подключены микросервисы должны иметь совместимость со стандартном DTP XA (distributed transaction processing) (XA - extended architecture), а также mq и кеши должны поддерживать XA драйвер
  • если приложение деплоится в Kubernetes, вам понадобиться управляющий механизм, который будет гарантировать, что есть только один экземпляр менеджера распределенных транзакций. Транзакционный менеджер должен быть всегда доступен и для него всегда должен быть обеспечен доступ к транзакционным логам. Пример Snowdrop Recovery Controller

Алгоритм работы двухфазного коммита:

  1. Код приложения в первую очередь обращается к координатору, получает номер транзакции. С этим номером обращается к остальным системам, например к нескольким разным микросервисам, просит их внести изменения с указанным идентификатором транзакции
  2. Затем приложение обращается к координатору с просьбой закоммитить транзакцию, после чего координатор сначала отправляет всем бдшкам сигнал prepare, если все сервисы ответили успехом (они захватили write locks на своих бд), то им посылается сигнал commit.
    Pasted image 20250530015346.png

Плюсы 2PC

  • является стандартным решением, существуют готовые менеджеры транзакций и множество бд его поддерживают
  • строгая консистентность данных

Минусы 2PC

  • сложная конфигурация
  • низкая производительность
  • ограниченная масштабируемость (практически невозможно масштабировать)
  • возможные отказы, при падении менеджера транзакций
  • не все системы имеют поддержку (например NoSQL не интегрируются, mq или кеши могут не поддерживать спецификацию)
  • особые требования в динамических окружениях, таких как Kubernetes

Оркестрация SAGA

Паттерн сага подразумевает формирование некой Саги которая будет включать в себя несколько действий на разных сервисах. В рамках неё каждый из микросервисов должен реализовать эндпоинт выполнения какого-то действия, а также эндпоинт отката этого действия.
В рамках паттерна подразумевается использование оркестратора, который бы имел эндпоинт выполнения саги и который бы производил последовательные выполнения элементов саги и откат в случае ошибки на одном из них
Оркестратор использует свою бд для хранения состояния изменений и он ответственен за восстановление при любом падении в процессе изменения состояния.

Pasted image 20250530020055.png
У нас есть сервис А, который выступает как оркестратор, он вызывает сервис Б и восстанавливается от падений с помощью компенсаторной операции при необходимости.

Ключевым моментом является то, что оба сервиса работают в своих локальных транзакциях
В данном случае запрос от сервиса А к сервису Б будет выполнен в рамках транзакции.

Сервисы участники данного паттерна должны предоставлять идемпотентные операции, поскольку возможно выполнение ретраев, также они должны предоставлять эндпоинты для отката транзакций, чтобы сохранять согласованность данных.

Плюсы Оркестрации:

  • управление состоянием между разными сервисами в распределенной системе
  • нет необходимости в XA транзакциях
  • всегда можно узнать в каком состоянии находится система
    Минусы Оркестрации:
  • сложная распределенная модель разработки
  • требует наличия идемпотентности и компенсаторных транзакций
  • согласованность только в конечном счете
  • возможны невосстанавливаемые падения (unrecoverable failures) при выполнении компенсаторных транзакций
  • нужно следить за наложением саг

Хореография SAGA

подразумевает что каждый из микросервисов должен генерировать события в некий брокер сообщений при начале выполнения некоторого действия или ошибках, а остальные должны реагировать соответствующим образом на полученные события.

Pasted image 20250530020529.png

Хореография с двойной записью

При работе хореографии встает логичный вопрос, а что делать сервису, сначала коммитить а потом слать сообщение или наоборот?

  • отправить сообщение затем закоммитить - является непрактичным решением, поскольку локальная транзакция может откатиться, а сообщение уже будет отправлено
  • закоммитить транзакцию и затем отправить сообщение - остается возможность падения приложения после коммита транзакции и перед отправкой сообщения. Однако данное решение лучше, поскольку можно задизайнить приложение таким образом, чтобы оно делала ретраи или откатывалось

Хореография без двойной записи

Одним из возможных решений является запись в рамках одной транзакции в бд и никуда не отправлять сообщение.

Пример - Сервис А сохраняет запрос и пишет в бд А. Сервис Б периодически опрашивает Сервис А и находит изменения, при обнаружении изменений Сервис Б обновляет свою бд.

Важнейшей частью данного подхода является то, что оба сервиса выполняют запись в свои бд в рамках локальных транзакций.

Плюсы Хореографии

  • убирает связность между реализацией и взаимодействием
  • нет единого координатора
  • улучшенная масштабируемость и устойчивость
  • проще при использовании соответствующих инструментов, таких как Debezium
    Минусы Хореографии
  • глобальное состояние системы распределено по сервисам-участникам (сложно узнать текущее состояние)
  • согласованность в конечном счете

Parallel pipelines

Предыдущие паттерны предлагают нам последовательную обработку входящего запроса, однако возможна ситуация когда один и тот же запрос должен обрабатываться разными сервисами параллельно.

Данный паттерн подразумевает возможность параллельно обрабатывать запросы, когда между сервисом А и сервисом Б нет прямой зависимости.

Добавляется сервис router, который будет посылать сообщения в оба сервиса в рамках одной транзакции.
Pasted image 20250530021715.png

Существует реализация параллельного пайплайна Listen to yourself . В рамках него один из сервисов слушает клиента и кладет сообщения в очередь, откуда читает как он сам так и другие сервисы пайплайна
Pasted image 20250530021923.png

Плюсы Parallel Pipelines:

  • простая, масштабируемая архитектура для параллельной обработки
    Минусы Parallel Pipelines:
  • сложно понять в каком состоянии находится системам в конкретный момент времени

Выводы

Нет единого подхода для решения проблемы распределенных транзакций.

Каждый паттерн имеет свои достоинства и недостатки, решая свою конкретную проблему.Pasted image 20250530022135.png